﻿using UnityEngine;
using System.Collections;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Reflection;
using System.Threading.Tasks;
using UnityEditor;
using UnityEditor.Experimental.GraphView;

public class SimpleHTTPServer
{
    const string default404Page = @"
<head>
<meta charset=""UTF-8"">
<style>
</style>
</head>
<body>
    <div>
    <div>
        <h4>{0}</h4>
    </div>
    </div>
</body>
";


    const string dirPage = @"
<head>
<meta charset=""UTF-8"">
<style>
</style>
</head>
<body>
    
    <div>
        <h>{0}</h>
        {1}
    </div>
</body>
";
    int bufferSize = 10;
    private readonly string[] _indexFiles =
    {
                    "index.html",
                    "index.htm",
                    "default.html",
                    "default.htm"
            };

    private static IDictionary<string, string> _mimeTypeMappings = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
            {
			#region extension to MIME type list
					{ ".asf", "video/x-ms-asf" },
                    { ".asx", "video/x-ms-asf" },
                    { ".avi", "video/x-msvideo" },
                    { ".bin", "application/octet-stream" },
                    { ".cco", "application/x-cocoa" },
                    { ".crt", "application/x-x509-ca-cert" },
                    { ".css", "text/css" },
                    { ".deb", "application/octet-stream" },
                    { ".der", "application/x-x509-ca-cert" },
                    { ".dll", "application/octet-stream" },
                    { ".dmg", "application/octet-stream" },
                    { ".ear", "application/java-archive" },
                    { ".eot", "application/octet-stream" },
                    { ".exe", "application/octet-stream" },
                    { ".flv", "video/x-flv" },
                    { ".gif", "image/gif" },
                    { ".hqx", "application/mac-binhex40" },
                    { ".htc", "text/x-component" },
                    { ".htm", "text/html" },
                    { ".html", "text/html" },
                    { ".ico", "image/x-icon" },
                    { ".img", "application/octet-stream" },
                    { ".svg", "image/svg+xml" },
                    { ".iso", "application/octet-stream" },
                    { ".jar", "application/java-archive" },
                    { ".jardiff", "application/x-java-archive-diff" },
                    { ".jng", "image/x-jng" },
                    { ".jnlp", "application/x-java-jnlp-file" },
                    { ".jpeg", "image/jpeg" },
                    { ".jpg", "image/jpeg" },
                    { ".js", "application/x-javascript" },
                    { ".mml", "text/mathml" },
                    { ".mng", "video/x-mng" },
                    { ".mov", "video/quicktime" },
                    { ".mp3", "audio/mpeg" },
                    { ".mpeg", "video/mpeg" },
                    { ".mp4", "video/mp4" },
                    { ".mpg", "video/mpeg" },
                    { ".msi", "application/octet-stream" },
                    { ".msm", "application/octet-stream" },
                    { ".msp", "application/octet-stream" },
                    { ".pdb", "application/x-pilot" },
                    { ".pdf", "application/pdf" },
                    { ".pem", "application/x-x509-ca-cert" },
                    { ".pl", "application/x-perl" },
                    { ".pm", "application/x-perl" },
                    { ".png", "image/png" },
                    { ".prc", "application/x-pilot" },
                    { ".ra", "audio/x-realaudio" },
                    { ".rar", "application/x-rar-compressed" },
                    { ".rpm", "application/x-redhat-package-manager" },
                    { ".rss", "text/xml" },
                    { ".run", "application/x-makeself" },
                    { ".sea", "application/x-sea" },
                    { ".shtml", "text/html" },
                    { ".sit", "application/x-stuffit" },
                    { ".swf", "application/x-shockwave-flash" },
                    { ".tcl", "application/x-tcl" },
                    { ".tk", "application/x-tcl" },
                    { ".txt", "text/plain" },
                    { ".war", "application/java-archive" },
                    { ".wbmp", "image/vnd.wap.wbmp" },
                    { ".wmv", "video/x-ms-wmv" },
                    { ".xml", "text/xml" },
                    { ".xpi", "application/x-xpinstall" },
                    { ".zip", "application/zip" },
			#endregion
			};

    private static SimpleHTTPServer httpServer;
    private string _rootDirectory;
    private HttpListener _listener;
    private int _port;

    public int Port
    {
        get { return _port; }
        private set { }
    }


    public static SimpleHTTPServer StartHttpServer(string path, int port)
    {
        if(httpServer == null)
        {
            httpServer = new SimpleHTTPServer(path, port);
        }

        return null;
    }

    public static void StopHttpServer()
    {
        if(httpServer != null)
        {
            httpServer.Stop();
            httpServer = null;
        }
    }



    /// <summary>
    /// Construct server with given port, path and buffer.
    /// </summary>
    /// <param name="path">The root folder path in your computer (Absolute path)</param>
    /// <param name="port">The port for your http server</param>
    private SimpleHTTPServer(string path, int port)
    {
        this.Initialize(path, port);
    }

    ~SimpleHTTPServer()
    {
        if(httpServer == this)
        {
            httpServer = null;
        }
    }

    public static string GetLocalIPAddress()
    {
        var host = Dns.GetHostEntry(Dns.GetHostName());
        foreach (var ip in host.AddressList)
        {
            if (ip.AddressFamily == AddressFamily.InterNetwork)
            {
                return ip.ToString();
            }
        }
        throw new Exception("No network adapters with an IPv4 address in the system!");
    }

    public static string GetHttpUrl(int port)
    {
        return $"http://{GetLocalIPAddress()}:{port}";
    }

    /// <summary>
    /// Stop Server
    /// </summary>
    public void Stop()
    {
        _listener.Abort();
        _listener.Stop();
    }

    private async Task<bool> Listen()
    {
        double startTime = 0;

        UnityEngine.Debug.Log($"start http server: {_rootDirectory} => {GetHttpUrl(_port)}");

        _listener = new HttpListener();
        _listener.Prefixes.Add("http://*:" + _port.ToString() + "/");
        _listener.Start();
        while (true)
        {
            try
            {
                var task = _listener.GetContextAsync();
                while(task.Status != System.Threading.Tasks.TaskStatus.RanToCompletion)
                {
                    if(!_listener.IsListening)
                    {
                        UnityEngine.Debug.Log("http server stoped");
                        return false;
                    }

                    if(EditorApplication.timeSinceStartup - startTime > 5)
                    {
                        startTime = EditorApplication.timeSinceStartup;
                        UnityEngine.Debug.Log("httpserver running...");
                    }
                    await Task.Yield();
                }
                HttpListenerContext context = task.Result;
                Process(context);
            }
            catch (Exception ex)
            {
                UnityEngine.Debug.Log(ex);
            }
        }

        return true;
    }

    private void Process(HttpListenerContext context)
    {
        string filename = context.Request.Url.AbsolutePath;
        filename = filename.Substring(1);

        if (string.IsNullOrEmpty(filename))
        {
            foreach (string indexFile in _indexFiles)
            {
                if (File.Exists(Path.Combine(_rootDirectory, indexFile)))
                {
                    filename = indexFile;
                    break;
                }
            }
        }


        filename = Path.Combine(_rootDirectory, filename);

        Dictionary<string, object> namedParameters = new Dictionary<string, object>();
        if (!string.IsNullOrEmpty(context.Request.Url.Query))
        {
            UnityEngine.Debug.Log(context.Request.Url.Query);
            var query = context.Request.Url.Query.Replace("?", "").Split('&');
            foreach (var item in query)
            {
                var t = item.Split('=');


                namedParameters.Add(t[0], t[1]);
            }
        }

        UnityEngine.Debug.Log("get: " + filename);

        if (File.Exists(filename))
        {
            TryServeFile();
        }
        else if(System.IO.Directory.Exists(filename))
        {
            
            var dirPaths = System.IO.Directory.GetDirectories(filename, "*", SearchOption.TopDirectoryOnly);
            string dirsStr = "";
            foreach(var d in dirPaths)
            {
                dirsStr += $"<div>{System.IO.Path.GetFileName(d)}</div>\n";
            }

            var filePaths = System.IO.Directory.GetFiles(filename, "*", SearchOption.TopDirectoryOnly);
            foreach (var d in filePaths)
            {
                dirsStr += $"<div>{System.IO.Path.GetFileName(d)}</div>\n";
            }

            var a = String.Format(dirPage, filename, dirsStr);

            byte[] resultByte = Encoding.UTF8.GetBytes(a);
            Stream resultStream = new MemoryStream(resultByte);
            context.Response.StatusCode = (int)HttpStatusCode.NotFound;
            context.Response.ContentType = "text/html";
            context.Response.ContentLength64 = resultByte.Length;
            context.Response.AddHeader("Date", DateTime.Now.ToString("r"));
            context.Response.AddHeader("Last-Modified", System.IO.File.GetLastWriteTime(filename).ToString("r"));

            byte[] buffer = new byte[1024 * bufferSize];
            int nbytes;
            while ((nbytes = resultStream.Read(buffer, 0, buffer.Length)) > 0)
                context.Response.OutputStream.Write(buffer, 0, nbytes);
            resultStream.Close();
        }
        else
        {
            var a = String.Format(default404Page, "not found: " + filename);
            byte[] resultByte = Encoding.UTF8.GetBytes(a);
            Stream resultStream = new MemoryStream(resultByte);
            context.Response.StatusCode = (int)HttpStatusCode.NotFound;
            context.Response.ContentType = "text/html";
            context.Response.ContentLength64 = resultByte.Length;
            context.Response.AddHeader("Date", DateTime.Now.ToString("r"));
            context.Response.AddHeader("Last-Modified", System.IO.File.GetLastWriteTime(filename).ToString("r"));

            byte[] buffer = new byte[1024 * bufferSize];
            int nbytes;
            while ((nbytes = resultStream.Read(buffer, 0, buffer.Length)) > 0)
                context.Response.OutputStream.Write(buffer, 0, nbytes);
            resultStream.Close();

        }
        context.Response.OutputStream.Flush();
        context.Response.OutputStream.Close();

        void TryServeFile()
        {
            try
            {
                context.Response.StatusCode = (int)HttpStatusCode.OK;
                Stream input = new FileStream(filename, FileMode.Open, FileAccess.Read);

                //Adding permanent http response headers
                string mime;
                context.Response.ContentType = _mimeTypeMappings.TryGetValue(Path.GetExtension(filename), out mime) ? mime : "application/octet-stream";
                context.Response.ContentLength64 = input.Length;
                context.Response.AddHeader("Date", DateTime.Now.ToString("r"));
                context.Response.AddHeader("Last-Modified", System.IO.File.GetLastWriteTime(filename).ToString("r"));

                byte[] buffer = new byte[1024 * bufferSize];
                int nbytes;
                while ((nbytes = input.Read(buffer, 0, buffer.Length)) > 0)
                    context.Response.OutputStream.Write(buffer, 0, nbytes);
                input.Close();

            }
            catch (Exception ex)
            {
                context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                UnityEngine.Debug.LogException(ex);
                context.Response.StatusDescription = ex.Message;
            }
        }
    }

    private void Initialize(string path, int port)
    {
        this._rootDirectory = path;
        this._port = port;

        this.Listen();
    }

}

